namespace ScrumSprintMonitor.Implementation
{
	#region #using Directives

	using System;
	using System.Collections.Generic;
	using System.Diagnostics;
	using System.Linq;

	using Infrastructure;

	using Microsoft.Practices.ServiceLocation;

	using Ninject.Core;

	#endregion

	public class NinjectContainerAbstraction : ServiceLocatorImplBase,
	                                           IDisposable
	{
		#region Private Fields

		private static readonly Dictionary<Type, bool> registeredModuleTypesDictionary = new Dictionary<Type, bool>();
		private static bool configurationDependentModulesAreSetUp;
		private static NinjectContainerAbstraction container;
		private readonly IKernel kernel;

		#endregion

		static NinjectContainerAbstraction()
		{
			ServiceLocator.SetLocatorProvider(EnsureContainerIsSetUp);
		}

		public NinjectContainerAbstraction()
		{
			this.kernel = new StandardKernel();

			LoadRegisteredModules(false);
		}

		public IKernel Instance
		{
			get { return this.kernel; }
		}

		#region Implementation of IDisposable

		public void Dispose()
		{
			if (container != null)
			{
				this.kernel.Dispose();

				container = null;
			}
		}

		#endregion

		public void LoadRegisteredModules(bool configurationDependent)
		{
			var moduleList = new List<IModule>();
			var selector = registeredModuleTypesDictionary.Where(kvp => kvp.Value == configurationDependent);

			selector.Select(kvp => kvp.Key).ToList().ForEach(type => moduleList.Add((IModule) Activator.CreateInstance(type)));
			moduleList.ForEach(module => this.kernel.Load(module));

			selector.ToList().ForEach(kvp => registeredModuleTypesDictionary.Remove(kvp.Key));
		}

		private void LoadConfigurationDependentModules()
		{
			LoadRegisteredModules(true);
		}

		public static NinjectContainerAbstraction EnsureContainerIsSetUp()
		{
			return container ?? (container = new NinjectContainerAbstraction());
		}

		public static void EnsureConfigurationDependentModulesAreSetUp()
		{
			EnsureContainerIsSetUp();

			if (!configurationDependentModulesAreSetUp)
			{
				configurationDependentModulesAreSetUp = true;

				container.LoadConfigurationDependentModules();
			}
		}

		public static void RegisterModule<T>()
		{
			RegisterModule<T>(false);
		}

		public static void RegisterModule<T>(bool configurationDependent)
		{
			registeredModuleTypesDictionary.Add(typeof(T), configurationDependent);
		}

		/// <summary>
		/// When implemented by inheriting classes, this method will do the actual work of resolving
		///             the requested service instance.
		/// </summary>
		/// <param name="serviceType">Type of instance requested.</param><param name="key">Name of registered service you want. May be null.</param>
		/// <returns>
		/// The requested service instance.
		/// </returns>
		protected override object DoGetInstance(Type serviceType, string key)
		{
			Debug.Assert(key == null);

			object service;

			try
			{
				if (serviceType == typeof(IServiceProvider))
				{
					return this;
				}

				service = this.kernel.Get(serviceType);
			}
			catch (Ninject.Core.ActivationException e)
			{
				this.kernel.Get<ILogger<NinjectContainerAbstraction>>().Warn(e, "Exception detected while activating service {0}.", serviceType.ToString());

				throw;
			}

			return service;
		}

		/// <summary>
		/// When implemented by inheriting classes, this method will do the actual work of
		///             resolving all the requested service instances.
		/// </summary>
		/// <param name="serviceType">Type of service requested.</param>
		/// <returns>
		/// Sequence of service instance objects.
		/// </returns>
		protected override IEnumerable<object> DoGetAllInstances(Type serviceType)
		{
			var service = GetInstance(serviceType);

			if (service != null)
			{
				yield return service;
			}
		}
	}
}